﻿using System;
using System.Linq;

namespace Sencha.Serialization.Serializers
{
	public sealed class PrimitiveSerializer<ValueT> : TypeSerializer<ValueT>
	{
		private static readonly bool IsEnum;
		private static readonly Action<IJsonWriter, ValueT> WriteFn;
		private static readonly Func<IJsonReader, ValueT> ReadFn;

		static PrimitiveSerializer()
		{
			var convertibleType = typeof(ValueT);
			if (convertibleType.IsGenericType && convertibleType.GetGenericTypeDefinition() == typeof(Nullable<>))
				throw new Exceptions.TypeContractViolation(typeof(PrimitiveSerializer<ValueT>), convertibleType, "can't be nullable type");

			IsEnum = convertibleType.IsEnum;
			if (IsEnum)
				convertibleType = Enum.GetUnderlyingType(convertibleType);

			var writeMethod = default(System.Reflection.MethodInfo);
			foreach (var m in typeof(JsonWriterExtentions).GetMethods())
			{
				var pm = m.GetParameters();
				if (m.IsGenericMethod || m.Name == "WriteMember" || pm.Length != 2 || pm[0].ParameterType != typeof(IJsonWriter) || pm[1].ParameterType != convertibleType)
					continue;

				writeMethod = m;
				break;
			}
			var readMethod = default(System.Reflection.MethodInfo);
			foreach (var m in typeof(JsonReaderBase).GetMethods())
			{
				var pm = m.GetParameters();
				if (m.IsGenericMethod || m.Name == "ReadMember" || pm.Length != 1 || pm[0].ParameterType != typeof(IJsonReader) || m.ReturnType != convertibleType)
					continue;

				readMethod = m;
				break;
			}

			if (writeMethod != null)
				WriteFn = (Action<IJsonWriter, ValueT>)Delegate.CreateDelegate(typeof(Action<IJsonWriter, ValueT>), writeMethod);
			if (readMethod != null)
				ReadFn = (Func<IJsonReader, ValueT>)Delegate.CreateDelegate(typeof(Func<IJsonReader, ValueT>), readMethod);
		}

		public override ValueT Deserialize(IJsonReader reader)
		{
			if (reader == null)
				throw new ArgumentNullException("reader");
			System.Diagnostics.Contracts.Contract.EndContractBlock();

			if (!IsEnum && ReadFn != null)
				return ReadFn(reader);
			else if (reader.Value.HasValue)
				return reader.Value.AsType<ValueT>();
			else
				throw new Exceptions.UnexpectedToken(reader, JsonToken.Boolean | JsonToken.DateTime | JsonToken.Null | JsonToken.Number | JsonToken.StringLiteral);
		}
		public override void Serialize(IJsonWriter writer, ValueT value)
		{
			if (writer == null)
				throw new ArgumentNullException("writer");
			System.Diagnostics.Contracts.Contract.EndContractBlock();

			if (IsEnum && (writer.Context.Features & Features.SerializeEnumsAsInteger) != 0 && WriteFn != null)
				WriteFn(writer, value);
			else if (WriteFn != null)
				WriteFn(writer, value);
			else
				writer.Write(TypeConvert.Convert<ValueT, string>(value, writer.Context.Format));
		}
	}
}
